#include "tty.h"

#define CRT_ADDR_REG 0x3d4
#define CRT_DATA_REG 0x3d5

#define CRT_START_ADDR_H 0xc
#define CRT_START_ADDR_L 0xd
#define CRT_CURSOR_H 0xe
#define CRT_CURSOR_L 0xf

#define VIDEO_MEM_BASE 0xb8000
#define VIDEO_MEM_SIZE 0x8000
#define VIDEO_MEM_END (VIDEO_MEM_BASE + VIDEO_MEM_SIZE)

#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25

#define ROW_SIZE (SCREEN_WIDTH * 2)
#define SCREEN_SIZE (ROW_SIZE * SCREEN_HEIGHT)

#define ASCII_NUL 0x00
#define ASCII_ENQ 0x05
#define ASCII_BEL 0x07 // \a
#define ASCII_BS 0x08  // \b
#define ASCII_HT 0x09  // \t
#define ASCII_LF 0x0A  // \n
#define ASCII_VT 0x0B  // \v
#define ASCII_FF 0x0C  // \f
#define ASCII_CR 0x0D  // \r
#define ASCII_DEL 0x7F

int x, y;
int screen;

static void set_screen()
{
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_H);
    out_byte(CRT_DATA_REG, (screen >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_L);
    out_byte(CRT_DATA_REG, (screen >> 1) & 0xff);
} 

static void set_cursor()
{
    int pos =  x * 2 + y * ROW_SIZE;
    out_byte(CRT_ADDR_REG, CRT_CURSOR_H);
    out_byte(CRT_DATA_REG, (pos >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_CURSOR_L);
    out_byte(CRT_DATA_REG, (pos >> 1) & 0xff);
}

// 向上滚屏
static void scroll_up()
{
    if (screen + SCREEN_SIZE + ROW_SIZE < VIDEO_MEM_END)
    {
        u32 *ptr = (u32 *)(screen + SCREEN_SIZE);
        for (size_t i = 0; i < SCREEN_WIDTH; i++)
        {
            *ptr++ = 0x0720;
        }
        screen += ROW_SIZE;
        y++;
    }
    else
    {
        // memcpy(VIDEO_MEM_BASE, screen + VIDEO_MEM_BASE, SCREEN_SIZE);
        // pos -= screen;
        // screen = MEM_BASE;
    }
    set_screen();
}

static void command_lf()
{
    if (y + 1 < SCREEN_HEIGHT)
    {
        y++;
        return;
    }
    scroll_up();
}

static void command_cr()
{
    x = 0;
}

static void command_bs()
{
    if (x)
    {
        x--;
        int pos =  x * 2 + y * ROW_SIZE;
        *(u16 *)pos = 0x0720;
    }
}

static void command_del()
{
    int pos =  x * 2 + y * ROW_SIZE;
    *(u16 *)pos = 0x0720;
}

void console_write(char *buf, u32 count)
{
    char ch;
    int pos =  VIDEO_MEM_BASE + (x * 2 + y * ROW_SIZE);
    char *ptr = (char *)pos;
    while (count--)
    {
        ch = *buf++;
        switch (ch)
        {
            case ASCII_NUL:
                break;
            case ASCII_BEL:
                break;
            case ASCII_BS:
                command_bs();
                break;
            case ASCII_HT:
                break;
            case ASCII_LF:
                command_lf();
                command_cr();
                break;
            case ASCII_VT:
                break;
            case ASCII_FF:
                command_lf();
                break;
            case ASCII_CR:
                command_cr();
                break;
            case ASCII_DEL:
                command_del();
                break;
            default:
                if (x >= SCREEN_WIDTH)
                {
                    x -= SCREEN_WIDTH;
                    pos -= ROW_SIZE;
                    command_lf();
                }

                *ptr = ch;
                ptr++;
                *ptr = 0x07;
                ptr++;

                pos += 2;
                x++;
                break;
        }
    }
    set_cursor();
}

void console_init()
{
    screen = 0;
    x = 0;
    y = 0;
    set_screen();
    set_cursor();

    u16* p = (u16*) VIDEO_MEM_BASE;
    for (int i = 0; i < SCREEN_SIZE / 2; i++)
    {
        *p++ = 0x0720;
    }
}